package com.yunpeng.aidevice.start;

import com.alibaba.fastjson.JSON;
import com.yunpeng.aidevice.cache.service.ILoginUserCacheService;
import com.yunpeng.aidevice.common.constant.ConfigConstantValues;
import com.yunpeng.aidevice.common.dto.AopRequestDTO;
import com.yunpeng.common.dto.Result;
import com.yunpeng.aidevice.common.dto.cache.LoginUserCacheDTO;
import com.yunpeng.aidevice.common.enums.ResultCodeEnum;
import com.yunpeng.common.exception.BizException;
import com.yunpeng.aidevice.permission.service.IPermissionOuterService;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * WebControllerAop
 * aop拦截器
 *
 * @author : Moore(hemw)
 * @date : 2020-03-11
 */
@Aspect
@Component
public class WebControllerAop {

    /**
     * 日志对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WebControllerAop.class);

    /**
     * 拦截验权白名单
     */
    private static final Map<String, Object> WHITE_LIST = new HashMap<String, Object>(16) {
        {
            put("com.yunpeng.aidevice.common.controllers.Index.defaultOk", null);
            put("com.yunpeng.aidevice.cms.web.controllers.MerchantSystemController.login", null);
            put("com.yunpeng.aidevice.bms.web.controllers.BizSystemController.login", null);
            put("com.yunpeng.aidevice.common.controllers.configs.PermissionController.clearUserPermissionCache", null);
            put("com.yunpeng.aidevice.common.controllers.users.UserController.deviceMerchantUserList", null);
            put("com.yunpeng.aidevice.common.controllers.JapanPadController.uploadRecordPath", null);
            put("com.yunpeng.aidevice.common.controllers.JapanPadController.queryDeviceSoftVersion", null);
            put("com.yunpeng.aidevice.common.controllers.JapanPadController.addNewDevice", null);
            put("com.yunpeng.aidevice.common.controllers.JapanPadController.dropDownList", null);
            put("com.yunpeng.aidevice.common.controllers.JapanPadController.getMerchantUserByPhone", null);
        }
    };

    /**
     * 统计请求的处理时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Value(value = "${debug}")
    private Boolean debug;

    @Resource
    private ILoginUserCacheService loginUserCacheService;

    @Resource
    private IPermissionOuterService permissionOuterService;

    /**
     * 指定切点
     * 匹配 com.example.demo.controller包及其子包下的所有类的所有方法
     * 一个超级大坑，aop拦截方法不设置为public的情况下，拦截会导致service注入失效（猜测是拦截了service注入的属性字段）
     */
    @Pointcut("execution(public * com.yunpeng.aidevice..*controllers..*(..))")
    public void webLog() {
    }

    /**
     * 前置通知，方法调用前被调用
     *
     * @param joinPoint
     */
    @Before("webLog()")
    public void before(JoinPoint joinPoint) throws BizException {
    }

    /**
     * 环绕通知,环绕增强，相当于MethodInterceptor
     *
     * @param proceedingJoinPoint
     * @return
     */
    @Around("webLog()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
        //获取目标方法的参数信息
        Object[] methodParams = proceedingJoinPoint.getArgs();
        //请求特征数据
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        //请求参数名
        String[] parameterNames = methodSignature.getParameterNames();

        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            throw new BizException(ResultCodeEnum.UNKNOWN_REQUEST);
        }
        HttpServletRequest req = attributes.getRequest();
        String accessToken = req.getHeader(ConfigConstantValues.ACCESS_TOKEN);

        AopRequestDTO aopRequestDTO = new AopRequestDTO(
                req.getRequestURL().toString(),
                req.getMethod(),
                getIpAddress(req),
                signature.getDeclaringTypeName() + "." + signature.getName(),
                parameterNames,
                methodParams,
                Thread.currentThread().getId());
        // 记录下请求内容
        LOGGER.info("收到http请求 : {}.", aopRequestDTO);
        ConfigConstantValues.THREAD_AOP_REQUEST_OBJECT.set(aopRequestDTO);
        try {
            if (!WHITE_LIST.containsKey(aopRequestDTO.getMappingClassAndMethod())) {
                // accessToken校验
                if (StringUtils.isBlank(accessToken)) {
                    return Result.fail(ResultCodeEnum.USER_NO_LOGIN);
                }
                //登录校验
                Result<LoginUserCacheDTO> loginUserCacheDtoResult = loginUserCacheService.get(accessToken);
                if (loginUserCacheDtoResult == null || !loginUserCacheDtoResult.isSuccess()) {
                    return Result.fail(ResultCodeEnum.USER_NO_LOGIN);
                }
                aopRequestDTO.setLoginUserCacheDTO(loginUserCacheDtoResult.getData());
//                String location = StringUtil.urlLocation(aopRequestDTO.getRequestUrl());
//                if (!permissionOuterService.checkPermission(location, loginUserCacheDtoResult.getData().getCode())) {
//                    return Result.fail(ResultCodeEnum.HAS_NOT_PERMISSION);
//                }
                ConfigConstantValues.THREAD_AOP_REQUEST_OBJECT.set(aopRequestDTO);
            }
            return proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            throw e;
        }
    }


    /**
     * 后置最终通知,final增强，不管是抛出异常或者正常退出都会执行
     *
     * @param jp
     */
    @After("webLog()")
    public void after(JoinPoint jp) {
        AopRequestDTO aopRequestDTO = ConfigConstantValues.THREAD_AOP_REQUEST_OBJECT.get();
        LOGGER.info("线程id:{} ,方法: {} ,方法执行时间 : {}ms.", aopRequestDTO.getThreadId(),
                aopRequestDTO.getRequestUrl(),
                (System.currentTimeMillis() - startTime.get()));
        ConfigConstantValues.THREAD_AOP_REQUEST_OBJECT.remove();
        startTime.remove();
    }


    /**
     * 处理完请求返回内容
     *
     * @param ret
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void afterReturning(Object ret) {
        // 处理完请求，返回内容
        AopRequestDTO aopRequestDTO = ConfigConstantValues.THREAD_AOP_REQUEST_OBJECT.get();
        LOGGER.info("线程id:{} ,方法: {} ,返回值: {} ,方法执行时间 : {}ms.", aopRequestDTO.getThreadId(), aopRequestDTO.getRequestUrl(),
                JSON.toJSONString(ret),
                (System.currentTimeMillis() - startTime.get()));
    }


    /**
     * 获取请求主机IP地址,如果通过代理进来，则透过防火墙获取真实IP地址;
     *
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request) {
        // 获取请求主机IP地址,如果通过代理进来，则透过防火墙获取真实IP地址
        String ip = request.getHeader(ConfigConstantValues.X_FORWARDED_FOR);
        if (ip == null || ip.length() == 0 || ConfigConstantValues.UNKNOWN.equalsIgnoreCase(ip)) {
            if (ip == null || ip.length() == 0 || ConfigConstantValues.UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader(ConfigConstantValues.PROXY_CLIENT_IP);
            }
            if (ip == null || ip.length() == 0 || ConfigConstantValues.UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader(ConfigConstantValues.WL_PROXY_CLIENT_IP);
            }
            if (ip == null || ip.length() == 0 || ConfigConstantValues.UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader(ConfigConstantValues.HTTP_CLIENT_IP);
            }
            if (ip == null || ip.length() == 0 || ConfigConstantValues.UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader(ConfigConstantValues.HTTP_X_FORWARDED_FOR);
            }
            if (ip == null || ip.length() == 0 || ConfigConstantValues.UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            if (ConfigConstantValues.LOCAL_ADDRESS.equalsIgnoreCase(ip)) {
                return ConfigConstantValues.LOCALHOST;
            }
        } else if (ip.length() > ConfigConstantValues.IP_LENGTH) {
            String[] ips = ip.split(",");
            for (int index = 0; index < ips.length; index++) {
                String strIp = (String) ips[index];
                if (!(ConfigConstantValues.UNKNOWN.equalsIgnoreCase(strIp))) {
                    ip = strIp;
                    break;
                }
            }
        }
        return ip;
    }

}
